package com.prb.tomcat.handler;

import com.prb.tomcat.container.Host;
import com.prb.tomcat.exception.InvalidHeaderException;
import com.prb.tomcat.protocol.IServlet;
import com.prb.tomcat.protocol.ProtocolTool;
import com.prb.tomcat.protocol.RequestHolder;
import com.prb.tomcat.protocol.ResponseRequestHolder;
import com.prb.tomcat.utils.FileUtil;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static com.prb.tomcat.protocol.StatusEnum.*;

public class ServerHandler {

    public static ServerHandler INSTANCE = new ServerHandler();

    private ServletHandler servletHandler = new DefaultServletHandler();

    private Host host;

    private static final int DEFAULT_BUFFER_SIZE = 4096;


    private static final String META_RESOURCE_DIR_PREFIX = "/meta/";

    private static Map<String , String> contentType = new HashMap<>();

    static {
        contentType.put(".html" , "text/html");
        contentType.put(".css" , "text/css");
        contentType.put(".js" , "application/javascript");
    }


    private ServerHandler(){}


    public void setHost(Host host) {
        this.host = host;
    }

    /**
     * 请求处理
     * @param selectionKey
     */
    public void request(SelectionKey selectionKey) throws IOException {
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
        channel.read(buffer);

        buffer.flip();
        byte[] bytes = new byte[buffer.limit()];
        buffer.get(bytes);
        String headerStr = new String(bytes);
        try {
            RequestHolder requestHolder = ProtocolTool.parseHeader(headerStr);
            selectionKey.attach(Optional.of(requestHolder));
        } catch (InvalidHeaderException e) {
            selectionKey.attach(Optional.empty());
        }
    }



    /**
     * 响应处理
     * @param selectionKey
     */
    public void response(SelectionKey selectionKey) throws IOException {

        SocketChannel channel = (SocketChannel) selectionKey.channel();
        Optional<RequestHolder> op = (Optional<RequestHolder>) selectionKey.attachment();

        // 处理无效请求，返回 400 错误
        if (!op.isPresent()) {
            ErrorHandler.handleBadRequest(channel);
            channel.close();
            return;
        }

        String ip = channel.getRemoteAddress().toString().replace("/", "");
        RequestHolder requestHolder = op.get();
        // 处理 403
        if (requestHolder.getPath().startsWith(META_RESOURCE_DIR_PREFIX)) {
            ErrorHandler.handleForbidden(channel);
            channel.close();
            log(ip, requestHolder, FORBIDDEN.getCode());
            return;
        }

        try {
            //处理静态资源
            if(isStatic(requestHolder.getPath())){
                handleStatic(channel, requestHolder.getPath());
            }
            //处理动态资源
            else{
                handleDynamic(channel , requestHolder);

            }

            log(ip, requestHolder, OK.getCode());
        } catch (FileNotFoundException e) {
            ErrorHandler.handleNotFound(channel);
            log(ip, requestHolder, NOT_FOUND.getCode());
        } catch (Exception e) {
            ErrorHandler.handleInternalServerError(channel);
            log(ip, requestHolder, INTERNAL_SERVER_ERROR.getCode());
        } finally {
            channel.close();
        }
    }

    //处理动态资源
    private void handleDynamic(SocketChannel channel, RequestHolder requestHolder) throws IOException {

        ResponseRequestHolder headers = new ResponseRequestHolder(OK.getCode());

        IServlet servlet = host.getContext("/tomcat").getServlet("/test");
        Object returnVal = servletHandler.handler(requestHolder , servlet);

        headers.setContentLength(33);
        headers.setContentType("application/json");
        ByteBuffer headerBuffer = ByteBuffer.wrap(headers.toString().getBytes());

        ByteBuffer bodyBuffer = ByteBuffer.wrap(headers.toString().getBytes());
        bodyBuffer.put(returnVal.toString().getBytes(Charset.defaultCharset()));
        bodyBuffer.flip();

        channel.write(new ByteBuffer[]{headerBuffer, bodyBuffer});
    }

    //处理200 返回
    private void handleStatic(SocketChannel channel, String path) throws IOException {
        ResponseRequestHolder headers = new ResponseRequestHolder(OK.getCode());

        ByteBuffer bodyBuffer = FileUtil.readFile(path);
        headers.setContentLength(bodyBuffer.capacity());
        headers.setContentType(getContentType(path));
        ByteBuffer headerBuffer = ByteBuffer.wrap(headers.toString().getBytes());

        channel.write(new ByteBuffer[]{headerBuffer, bodyBuffer});
    }


    //获取静态资源的 content type
    private String getContentType(String path) {
        return contentType.get(path.substring(path.indexOf(".")));
    }

    private void log(String ip, RequestHolder requestHolder, int code) {
        // ip [date] "Method path version" code user-agent
        String dateStr = Date.from(Instant.now()).toString();
        String msg = String.format("%s [%s] \"%s %s %s\" %d %s",
                ip, dateStr, requestHolder.getMethod(), requestHolder.getPath(), requestHolder.getVersion(), code, requestHolder.getHeader("User-Agent"));
        System.out.println(msg);
    }


    //判断是否为静态资源
    private boolean isStatic(String path) {
        return path.endsWith(".html") || path.endsWith(".js") || path.endsWith(".css");
    }
}
